package com.hsurosy.hsuchain.nft.lock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.StandardReflectionParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁切面
 *
 * @Author Hsu琛君珩
 * @Date 2024-10-05 4:40
 * @Description
 * @Version: v1.0.0
 */
@Aspect
@Component
public class DistributeLockAspect {

    private RedissonClient redissonClient;

    public DistributeLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    private static final Logger LOG = LoggerFactory.getLogger(DistributeLockAspect.class);

    /**
     * AOP切面拦截带有 @DistributeLock 注解的方法，实现分布式锁功能
     *
     * @param pjp 连接点信息
     * @return 方法执行结果
     * @throws Exception 抛出异常
     */
    @Around("@annotation(cn.hollis.nft.turbo.lock.DistributeLock)")
    public Object process(ProceedingJoinPoint pjp) throws Exception {
        Object response = null;

        // 获取被调用的方法
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();

        // 获取方法上的分布式锁注解
        DistributeLock distributeLock = method.getAnnotation(DistributeLock.class);

        // 解析锁的key
        String key = distributeLock.key();
        if (DistributeLockConstant.NONE_KEY.equals(key)) {
            // 如果没有直接定义key，则通过SpEL表达式解析
            if (DistributeLockConstant.NONE_KEY.equals(distributeLock.keyExpression())) {
                throw new DistributeLockException("no lock key found...");
            }
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(distributeLock.keyExpression());

            // 创建上下文并绑定方法参数
            EvaluationContext context = new StandardEvaluationContext();
            Object[] args = pjp.getArgs();

            // 获取方法参数名称
            StandardReflectionParameterNameDiscoverer discoverer = new StandardReflectionParameterNameDiscoverer();
            String[] parameterNames = discoverer.getParameterNames(method);

            // 将方法参数绑定到表达式上下文
            if (parameterNames != null) {
                for (int i = 0; i < parameterNames.length; i++) {
                    context.setVariable(parameterNames[i], args[i]);
                }
            }

            // 通过SpEL解析锁的key
            key = String.valueOf(expression.getValue(context));
        }

        // 获取锁场景，并构造最终锁的key
        String scene = distributeLock.scene();
        String lockKey = scene + "#" + key;

        // 获取锁的超时时间和等待时间
        int expireTime = distributeLock.expireTime();
        int waitTime = distributeLock.waitTime();

        // 获取Redisson锁对象
        RLock rLock = redissonClient.getLock(lockKey);
        boolean lockResult = false;

        // 尝试加锁，处理不同的等待时间和过期时间场景
        if (waitTime == DistributeLockConstant.DEFAULT_WAIT_TIME) {
            if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                LOG.info(String.format("lock for key : %s", lockKey));
                rLock.lock();
            } else {
                LOG.info(String.format("lock for key : %s , expire : %s", lockKey, expireTime));
                rLock.lock(expireTime, TimeUnit.MILLISECONDS);
            }
            lockResult = true;
        } else {
            if (expireTime == DistributeLockConstant.DEFAULT_EXPIRE_TIME) {
                LOG.info(String.format("try lock for key : %s , wait : %s", lockKey, waitTime));
                lockResult = rLock.tryLock(waitTime, TimeUnit.MILLISECONDS);
            } else {
                LOG.info(String.format("try lock for key : %s , expire : %s , wait : %s", lockKey, expireTime, waitTime));
                lockResult = rLock.tryLock(waitTime, expireTime, TimeUnit.MILLISECONDS);
            }
        }

        // 加锁失败处理
        if (!lockResult) {
            LOG.warn(String.format("lock failed for key : %s , expire : %s", lockKey, expireTime));
            throw new DistributeLockException("acquire lock failed... key : " + lockKey);
        }

        // 执行目标方法
        try {
            LOG.info(String.format("lock success for key : %s , expire : %s", lockKey, expireTime));
            response = pjp.proceed();
        } catch (Throwable e) {
            throw new Exception(e);
        } finally {
            // 释放锁
            rLock.unlock();
            LOG.info(String.format("unlock for key : %s , expire : %s", lockKey, expireTime));
        }
        return response;
    }
}
